#region Usings

using System;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Collections;
using System.Runtime.InteropServices;

#endregion

namespace AlternativePorts.Threading
{
    public sealed class ThreadPool : IDisposable
    {
        #region Fields

        private short _maxThreads = 2;
        private short _threadCount = 0;
        private ArrayList _tasks = new ArrayList();
        private readonly ArrayList _availableThreads = new ArrayList();
        private readonly ArrayList _createdThreads = new ArrayList();
        private short _availableThreadCount;
        private bool _disposed = false;
            
        #endregion

        #region Constructor

        public ThreadPool()
            : this(-1)
        {
        }

        public ThreadPool(short maxThreads)
        {
            _maxThreads = maxThreads;
            _availableThreadCount = maxThreads == -1 ? short.MaxValue : maxThreads;
        }

        #endregion

        #region Properties

        public short AvailableThreadCount
        {
            get
            {
                return _availableThreadCount;
            }
        }

        public short ThreadCount
        {
            get { return _threadCount; }
        }

        public short MaxThreads
        {
            get { return _maxThreads; }
        }

        #endregion

        #region Methods

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void EnqueueTask(Task task)
        {
            if(_disposed)
                throw new InvalidOperationException("Task was added when threadpool has been disposed.");

            lock (_tasks)
            {
                _tasks.Insert(0, task);

                // wake up any threads that are currently waiting for tasks or create a new one
                lock (_availableThreads)
                {
                    // if there are already threads waiting for execution, wake one of them up
                    if (_availableThreads.Count != 0)
                    {
                        ((Thread)_availableThreads[_availableThreads.Count - 1]).Resume();
                        _availableThreads.RemoveAt(_availableThreads.Count - 1);
                        _availableThreadCount--;
                    }
                    // if there is no thread available and we havent reached maximum thread count yet, create one
                    else if (ThreadCount < MaxThreads)
                    {
                        Action a = new Action(this);
                        Thread t = new Thread(new ThreadStart(a.Execute));
                        _createdThreads.Add(t);
                        _threadCount++;
                        t.Start();
                    }
                }
            }           
        }
            
        #endregion

        #region Class

        private class Action : IExecutable
        {
            private ThreadPool _threadPool = null;
            private bool _firstRun = true;

            public Action(ThreadPool threadPool)
            {
                _threadPool = threadPool;
            }

            #region IExecutable Members

            public void Execute()
            {
                //run as long as pool is not disposed
                while (!_threadPool._disposed)
                {
                    Monitor.Enter(_threadPool._tasks);
                    Task currentTask = null;

                    while (true)
                    {
                        //stop working if threadpool has been disposed
                        if(_threadPool._disposed)
                            return;

                        // if there is no task available
                        if (_threadPool._tasks.Count == 0)
                        {
                            // ad current thread to available threads
                            lock (_threadPool._availableThreads)
                            {
                                _threadPool._availableThreads.Insert(0, Thread.CurrentThread);
                                if (!_firstRun)
                                {
                                    _threadPool._availableThreadCount++;
                                    _firstRun = false;
                                }

                            }

                            Monitor.Exit(_threadPool._tasks);
                            Thread.CurrentThread.Suspend();
                            //is a workaround: normally, each thread has a time slice of 20 milliseconds in .netmf. Threrfore a thread would
                            //continue to work until its timeslice expires although we called Suspend(); The current known workaround for this
                            //is to call Thread.Sleep(0) immediately after the Suspend(); method! :)
                            Thread.Sleep(0);

                            //if threadpool was disposed, simply return;
                            if(_threadPool._disposed)
                                return;

                            // upon being woken up, re-lock the tasks and try to get one
                            Monitor.Enter(_threadPool._tasks);
                        }
                        else
                        {
                            // take one task from the tasklist
                            currentTask = (Task)_threadPool._tasks[_threadPool._tasks.Count - 1];
                            _threadPool._tasks.RemoveAt(_threadPool._tasks.Count - 1);
                            _firstRun = false;

                            // remove the lock from the tasklist
                            Monitor.Exit(_threadPool._tasks);

                            // execute the while loop
                            break;
                        }
                    }

                    try
                    {
                        //Execute the task;
                        currentTask.Execute();
                    }
                    catch(Exception ex)
                    {
                        ////remove thread which returns the Exception
                        //lock (_threadPool._createdThreads)
                        //    _threadPool._createdThreads.Remove(Thread.CurrentThread);

                        //log exception
                        //Debug.Print(ex.Message);
                        ////and rethrow it
                        //throw ex;
                    }
                }
            }

            #endregion
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_disposed)
                return;
            _disposed = true;

            //at first remove all tasks so no new worker is started
            lock (_tasks) _tasks.Clear();

            foreach (Thread t in _createdThreads)
            {
                //if workre is currently running
                if (!_availableThreads.Contains(t))
                    try
                    {
                        t.Abort();
                    }
                    catch (ThreadAbortException)
                    {}
            }


            //resume sleeping threads so they can quit
            foreach (Thread t in _availableThreads)
                t.Resume();

            _availableThreads.Clear();
            _createdThreads.Clear();
        }

        #endregion
    } 
}
